跳到主要内容位置

TCP

OSI七层协议#

物理层#

主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(即由1、0转化为强弱电流来进行传输,到达目的后再转化为1、0,也就说我们常说的模数转换)。这一层的数据成为比特,网卡工作在此层。物理层一般较少关心网络入侵分析,更专注于保证设备的电缆安全。

数据链路层#

主要是将从物理层接收的数据进行MAC地址的封装与解封装。这一层的数据常被称为帧。在这一层工作的设备是交换机,数据通过交换机来传输(三层交换机不在此层工作)

网络层#

主要用于将从下层接收到的数据进行IP地址的封装与解封装。在这一层工作的设备是路由器,这一层的数据常被称为数据包。

传输层#

本层定义了一些传输数据的协议和端口号(如http端口80等),比如:TCP(传输控制协议,传输效率低,可靠性高,用于传输对可靠性要求高且数据量大的数据)和UDP(用于数据包协议,与TCP的特性相反,传输的是对可靠性要求不高且数据量小的数据)。传输层主要是将从下层接收到的数据进行分段传输,到达目的地后再重组。我们常把这一层的数据成为段。

会话层#

通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。主要是在系统之间发起会话或接收会话请求(设备之间可通过IP,也可通过MAC或主机名来互相认识)。

表示层#

主要是对接收的数据进行解释、加密与解密、压缩与解压缩等操作(也就是把计算机能够识别的东西转换成人能够识别的东西,比如图片、声音等)

应用层#

主要是一些终端的应用,比如FTP(文件下载)、WEB(网页浏览)、QQ之类的应用(也可以把它理解成我们在电脑屏幕上所看到的东西,也就是终端应用),也可以理解为应用层是负责向用户或应用程序显示数据的。

TCP请求头#

image-20210406073301946

  • 序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。

  • 确认应答号:指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决不丢包的问题。

  • 控制位:

  • ACK:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1

  • RST:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接。

  • SYC:该位为 1 时,表示希望建立连,并在其「序列号」的字段进行序列号初始值的设定。

  • FIN:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位置为 1 的 TCP 段。

TCP是面向连接的、可靠的、基于字节流的传输层通信协议#

  • 面向连接:意味着两个使用TCP的应用(通常是一个客户和一个服务器)在彼此交换数据之前必须先建立一个TCP连接。在一个TCP连接中,仅有两方进行彼此通信。广播和多播不能用于TCP。
  • 可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;
  • 字节流:消息是「没有边界」的,所以无论我们消息有多大都可以进行传输。并且消息是「有序的」,当「前一个」消息没有收到的时候,即使它先收到了后面的字节已经收到,那么也不能扔给应用层去处理,同时对「重复」的报文会自动丢弃。

TCP和UDP区别#

  1. 连接

    • TCP 是面向连接的传输层协议,传输数据前先要建立连接。

    • UDP 是不需要连接,即刻传输数据。

  2. 服务对象

    • TCP 是一对一的两点服务,即一条连接只有两个端点。

    • UDP 支持一对一、一对多、多对多的交互通信

  3. 可靠性

    • TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按需到达。

    • UDP 是尽最大努力交付,不保证可靠交付数据。

  4. 拥塞控制、流量控制

    • TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。

    • UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。

  5. 首部开销

    • TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。

    • UDP 首部只有 8 个字节,并且是固定不变的,开销较小。

简单回答:

  • TCP面向连接,可靠的,有序的,以字节流方式发送数据(如打电话要先拨号建立连接)速度慢;较重量;全双工;适用于文件传输、浏览器等
  • UDP是无连接的,不保证可靠,即发送数据之前不需要建立连接,面向报文;速度快;轻量;适用于即时通讯、视频通话等

TCP是通过什么方式来提供可靠传输的#

1.重传机制#

TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。

2.校验和#

TCP收到数据,它将发送一个确认。这个确认不是立即发送,通常将推迟几分之一秒。(对于收到的请求,给出确认响应)(之所以推迟,可能是要对包做完整校验)TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段(希望发端超时并重发)。 (校验出包有错,丢弃报文段,不给出响应,TCP发送数据端,超时时会重发数据)

3.序列号和确认应答#

序列号:TCP传输时将每个字节的数据都进行了编号。

确认应答:TCP传输的过程中,每次接收方收到数据后,都会对传输方进行确认应答。也就是发送ACK报文。这个ACK报文当中带有对应的确认序列号,告诉发送方,接收到了哪些数据,下一次的数据从哪里发。

4.滑动窗口#

TCP还能提供流量控制。TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲区溢出。

TCP使用的流量控制协议是可变大小的滑动窗口协议。

5.拥塞控制#

拥塞避免和流量控制这两种机制很像,但是流量控制是由接收方的接受能力也就是接收窗口所决定的,如果接收窗口够大,以动态调整发送窗口的大小调整发送速度

拥塞避免主要由网络情况所限制,网络情况良好,则加大发送速率,网络状态差(冗余 ACK 和丢包)则降低发送速率(慢启动,拥塞控制,快恢复,快重传)

窗口分为滑动窗口和拥塞窗口#

滑动窗口指的是接收端使用的窗口大小,用来告知发送端自己的缓存大小,以此可以控制发送端发送数据的大小,从而达到流量控制的目的。

拥塞窗口指的是发送端使用的窗口大小,拥塞窗口不代表缓存,拥塞窗口指某一源端数据流在一个RTT内可以最多发送的数据包数

慢开始#

慢启动算法的基本思想是当TCP开始在一个网络中传输数据或发现数据丢失并开始重发时,首先慢慢的对网路实际容量进行试探,避免由于发送了过量的数据而导致阻塞。

发送方取拥塞窗口与通告窗口中的最小值作为发送上限。拥塞窗口(cwnd)是发送方使用的流量控制,而通告窗口则是接收方使用的流量控制。发送方开始时发送一个报文段,然后等待 ACK。当收到该ACK时,拥塞窗口从1增加为2,即可以发送两个报文段。当收到这两个报文段的 ACK时,拥塞窗口就增加为4。这是一种指数增加的关系。

拥塞避免#

20210407172539-gigapixel-scale-2_00x

在该图中,假定当cwnd为32个报文段时就会发生拥塞。于是设置 ssthresh为16个报文段,而cwnd为1个报文段。在时刻0发送了一个报文段,并假定在时刻1接收到它的ACK,此时cwnd增加为2。接着发送了2个报文段,并假定在时刻2接收到它们的ACK,于是cwnd增加为4(对每个ACK增加1次)。这种指数增加算法一直进行到第4秒后cwnd等于ssthresh时才停止,从该时刻起,cwnd以线性方式增加,在每个往返时间内最多增加 1个报文段。如果此时出现了网络拥塞,那么cwnd会被置为1,重新开启慢启动,ssthresh被砍半。

快重传#

快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时才进行捎带确认。

2

接收方收到了M1和M2后都分别发出了确认。现在假定接收方没有收到M3但接着收到了M4。显然,接收方不能确认M4,因为M4是收到的失序报文段,所以接收方应及时发送对M2的重复确认,这样做可以让 发送方及早知道报文段M3没有到达接收方。发送方接着发送了M5和M6。接收方收到这两个报文后,也还要再次发出对M2的重复确认。这样,发送方共收到了 接收方的四个对M2的确认,其中后三个都是重复确认。

快恢复#

当发送方连续收到三个重复确认,就执行"乘法减小"算法,把慢开始门限ssthresh减半,与慢开始不同之处是现在不执行慢开始算法(即拥塞窗口cwnd现在不设置为1),而是把cwnd值设置为慢开始门限ssthresh减半后的数值,然后开始执行拥塞避免算法("加法增大"),使拥塞窗口缓慢地线性增大

3

三次握手目的和本质#

过程描述#

  • 首先 Client 端发送连接请求报文,
  • Server 段接受连接后回复 ACK 报文,并为这次连接分配资源。
  • Client 端接收到 ACK 报文后也向 Server 段发生 ACK 报文,并分配资源,这样 TCP 连接就建立了。

11-gigapixel-scale-2_00x#

  • 小结三次握手的关键是要确认对方收到了自己的数据包,这个目标就是通过“确认号(Ack)”字段实现的。计算机会记录下自己发送的数据包序号Seq,待收到对方的数据包后,检测“确认号(Ack)”字段,看Ack = Seq + 1是否成立,如果成立说明对方正确收到了自己的数据包

握手为什么要三次,两次行不行?#

这道题有以下两种说法:

三次握手主要是为了确定客户端以及服务端的接收能力和发送能力都没问题

第一次握手:客户端发送报文,服务端接收报文,确认了客户端的发送能力以及服务端的接收能力都没问题;

第二次握手:服务端发送报文,客户端接收报文,客户端确认了自己的发送能力和接收能力和服务端的接收能力以及发送能力没问题,但是服务端没法确认客户端的接收能力是否正常。

第三次握手:服务端接收到客户端发送的确认报文,服务端确认了客户端的发送和接收能力都没问题

原因一:避免历史连接#

首要原因是为了防止旧的重复连接初始化造成混乱。

网络环境是错综复杂的,往往并不是如我们期望的一样,先发送的数据包,就先到达目标主机,反而它很骚,可能会由于网络拥堵等乱七八糟的原因,会使得旧的数据包,先到达目标主机,那么这种情况下 TCP 三次握手是如何避免的呢?

客户端连续发送多次 SYN 建立连接的报文,在网络拥堵等情况下:

  • 一个「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端;
  • 那么此时服务端就会回一个 SYN + ACK 报文给客户端;
  • 客户端收到后可以根据自身的上下文,判断这是一个历史连接(序列号过期或超时),那么客户端就会发送 RST 报文给服务端,表示中止这一次连接。

所以如果是两次握手连接,就不能判断当前连接是否是历史连接

原因二:同步双方初始序列号#

TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号是可靠传输的一个关键因素,它的作用:

  • 接收方可以去除重复的数据;
  • 接收方可以根据数据包的序列号按序接收;
  • 可以标识发送出去的数据包中, 哪些是已经被对方收到的;

可见,序列号在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带「初始序列号」的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送「初始序列号」给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。

四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了「三次握手」。

而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。

原因三:避免资源浪费#

如果只有「两次握手」,当客户端的 SYN 请求连接在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN ,由于没有第三次握手,服务器不清楚客户端是否收到了自己发送的建立连接的 ACK 确认信号,所以每收到一个 SYN 就只能先主动建立一个连接,这会造成什么情况呢?

如果客户端的 SYN 阻塞了,重复发送多次 SYN 报文,那么服务器在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。

什么是四次挥手#

建立连接时需要三次握手,断开连接时需要四次挥手,这由TCP的半关闭(half-close)造成的。所谓的半关闭,其实就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。

第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号

第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,

第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号

第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,,此时客户端处于 TIME_WAIT 状态,需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。

过程描述

  • A:“任务处理完毕,我希望断开连接。”
  • B:“哦,是吗?请稍等,我准备一下。”
  • 等待片刻后……
  • B:“我准备好了,可以断开连接了。”
  • A:“好的,谢谢合作。”

44

挥手为什么是四次?#

  • 因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。
  • 但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。
  • 只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手

为什么需要保持2MSL?#

  1. 是因为有可能客户端发往服务器的 ACK 丢失,服务器并不知道客户端已经确认关闭,这时候客户端的关闭会导致服务器端无法正常关闭。
  2. 是为了保证连接中的报文都已经传递。假如短时间关闭又重新实现一个 TCP 还连到了同个端口上,旧连接中尚未消失的数据就会被认为是新连接的数据。

什么是MSL#

MSL 是“报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为tcp报文 (segment)是ip数据报(datagram)的数据部分。

IP头中有一个TTL域,中文可以译为“生存时间”,这个生存时间是由源主机设置初始值但不是存的具体时间,而是存储了一个IP数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减1,当此值为0则数据报将被丢弃,同时发送ICMP报文通知源主机。RFC 793中规定MSL为2分钟,实际应用中常用的是30秒,1分钟和2分钟等。

注意:MSL 的单位是时间,而 TTL 是经过路由跳数。所以 MSL 应该要大于等于 TTL 消耗为 0 的时间,以确保报文已被自然消亡。

为什么需要 TIME_WAIT 状态?#

主要是两个原因:

  • 防止具有相同「四元组」的「旧」数据包被收到;

1

  • 保证「被动关闭连接」的一方能被正确的关闭,即保证最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭;

2

ISN#

三次握手的一个重要功能是客户端和服务端交换ISN(Initial Sequence Number), 以便让对方知道接下来接收数据的时候如何按序列号组装数据。

ISN = M + F(localhost, localport, remotehost, remoteport)

M是一个计时器,每隔4毫秒加1。

F是一个Hash算法,根据源IP、目的IP、源端口、目的端口生成一个随机数值。要保证hash算法不能被外部轻易推算得出。

  • MTU: Maximum Transmit Unit,最大传输单元,即物理接口(数据链路层)提供给其上层(通常是IP层)最大一次传输数据的大小;以普遍使用的以太网接口为例,缺省MTU=1500 Byte,这是以太网接口对IP层的约束,如果IP层有<=1500 byte 需要发送,只需要一个IP包就可以完成发送任务;如果IP层有> 1500 byte 数据需要发送,需要分片才能完成发送,这些分片有一个共同点,即IP Header ID相同。
  • MSS:Maximum Segment Size ,TCP提交给IP层最大分段大小,不包含TCP Header和 TCP Option,只包含TCP Payload ,MSS是TCP用来限制application层最大的发送字节数。如果底层物理接口MTU= 1500 byte,则 MSS = 1500- 20(IP Header) -20 (TCP Header) = 1460 byte,如果application 有2000 byte发送,需要两个segment才可以完成发送,第一个TCP segment = 1460,第二个TCP segment = 540。

粘包和拆包#

假设客户端分别发送数据包D1和D2给服务端,由于服务端一次性读取到的字节数是不确定的,所以可能存在以下4种情况。

  1. 服务端分2次读取到了两个独立的包,分别是D1,D2,没有粘包和拆包;
  2. 服务端一次性接收了两个包,D1和D2粘在一起了,被成为TCP粘包;
  3. 服务端分2次读取到了两个数据包,第一次读取到了完整的D1和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为拆包;
  4. 服务端分2次读取到了两个数据包,第一次读取到了部分D1,第二次读取D1剩余的部分和完整的D2包;
  5. 如果此时服务端TCP接收滑动窗非常小,而数据包D1和D2都很大,很有可能发送第五种可能,即服务端多次才能把D1和D2接收完全,期间多次发生拆包情况。

解决策略#

由于底层的TCP无法理解上层的业务逻辑,所以在底层是无法确保数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,如下:

  1. 消息定长,例如每个报文的大小为固定长度200字节,如果不够,空位补空格。
  2. 在包尾增加回车换行符进行分割,例如FTP协议。
  3. 将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长度)的字段,通常设计思路是消息头的第一个字段用int来表示消息的总长度。
  4. 更复杂的应用层协议。